# -*- coding: utf-8 -*-
'''
Created on 10 Mar 2011

@author: serj
'''
import re
import logging
import traceback
from waveapi.blip import Annotation
from waveapi import element
from xml.dom import minidom
from xml.dom import Node
from elementProperties import LineProps
from elementProperties import AttachmentProps
import urllib2

html_escape_table = {
    u"&": '&#38;',
    u'"': '&#34;',
    u"'": '&#39;',
    u'>': '&#62;',
    u'<': '&#60;',
    u'¢': '&#162;',
    u'£': '&#163;',
    u'¥': '&#165;',
    u'€': '&#8364;',
    u'§': '&#167;',
    u'©': '&#169;',
    u'®': '&#174;',
    u'™': '&#8482;',
}
#    u"&": '&amp;',
#    u'"': '&quot;',
#    u"'": '&apos;',
#    u'>': '&gt;',
#    u'<': '&lt;',
#    u'¢': '&cent;',
#    u'£': '&pound;',
#    u'¥': '&yen;',
#    u'€': '&euro;',
#    u'§': '&sect;',
#    u'©': '&copy;',
#    u'®': '&reg;',
#    u'™': '&trade;',
#}

DOCUMENT_CUT = '----'

class Cutter(element.Element):
    class_type = 'CUTTER'
    def __init__(self, caption=None, data=None, mimeType=None, attachmentId=None,
                attachmentUrl=None):
        super(Cutter, self).__init__(Cutter.class_type)


def html_escape(text):
    """Produce entities within text."""
    return "".join(html_escape_table.get(c, c) for c in text)


ANNOTATION_MAP = {
    Annotation.BACKGROUND_COLOR: 'background-color: %s; ',
    Annotation.COLOR: 'color: %s; ',
    Annotation.FONT_FAMILY: 'font-family: %s; ',
    Annotation.FONT_SIZE: 'font-size: %s; ',
    Annotation.FONT_STYLE: 'font-style: %s; ',
    Annotation.FONT_WEIGHT: 'font-weight: %s; ',
    Annotation.VERTICAL_ALIGN: 'text-align: %s; ',
    Annotation.TEXT_DECORATION: 'text-decoration: %s; '
}


LINE_ALIGNMENT_MAP = {
    'c': 'center',
    'r': 'right',
    'l': 'left'
}


VALID_ANNOTATION_NAMES = [
    Annotation.COLOR,
    Annotation.FONT_FAMILY,
    Annotation.FONT_SIZE,
    Annotation.FONT_STYLE,
    Annotation.FONT_WEIGHT,
    Annotation.VERTICAL_ALIGN,
    Annotation.TEXT_DECORATION
]


def _write_data_replacer(writer, data, useNative=True):
    writer.write(data)


minidom.__dict__['_write_data'] = _write_data_replacer

MARK = '+++'

class SimpleBlipParser(object):
    START = 0
    END = 1
    VALID_ELEMENTS_TYPES = [element.Image, element.Attachment, element.Line, Cutter]
    REMOVED_ANNOTATIONS = ['lang', 'conv/title', 'spell']
    LINK_ANNOTATIONS = [Annotation.LINK, 'link/auto']
    PRIORITY_ANNOTATIONS = LINK_ANNOTATIONS

    def __init__(self):
        self.processed_blips = []
        self.cut_tag = None

    def __initRange(self, blip, cut_tag=None):
        element_range = {}
        if cut_tag:
            cut_index = blip.text.find(DOCUMENT_CUT)
            if cut_index >= 0:
                blip.range(cut_index, cut_index + len(DOCUMENT_CUT)).replace('')
        last_element = None
        ''' Adding elements '''
        for i in range(len(blip)):
            blipRefs = blip[i].value()
#            if not isinstance(blipRefs, element.Element): continue
#            logging.info(i)
#            if type(blipRefs) != unicode:
#                logging.info(blipRefs.serialize())
#            else:
#                logging.info(blipRefs)
            if type(blipRefs) not in SimpleBlipParser.VALID_ELEMENTS_TYPES:
                if type(blipRefs) == element.Element:
                    if(blipRefs.type == element.Element.INLINE_BLIP_TYPE):
                        # processing inline blip
                        self.__addToRange(element_range, i, SimpleBlipParser.START, blipRefs)
                continue
            self.__addToRange(element_range, i, SimpleBlipParser.START, blipRefs)
            if type(blipRefs) == element.Line:
#                lastElement = blipRefs
#            elif type(lastElement) == element.Line:
                if last_element:
                    self.__addToRange(element_range, i, SimpleBlipParser.END, last_element)
                last_element = blipRefs
        if last_element:
            self.__addToRange(element_range, len(blip), SimpleBlipParser.END, last_element)

        ''' Adding priority annotations '''
        for key in SimpleBlipParser.PRIORITY_ANNOTATIONS:
            if not blip.annotations.has_key(key): continue
            for annotation in blip.annotations[key]:
                self.__addToRange(element_range, annotation.start, SimpleBlipParser.START, annotation)
                self.__addToRange(element_range, annotation.end, SimpleBlipParser.END, annotation)
        ''' Adding rest annotations '''
        for annotation in blip.annotations:
            if annotation.name not in VALID_ANNOTATION_NAMES:
#                logging.info('skipped annotation: %s' % annotation.name)
                continue
            self.__addToRange(element_range, annotation.start, SimpleBlipParser.START, annotation)
            self.__addToRange(element_range, annotation.end, SimpleBlipParser.END, annotation)
        return element_range


    def __addToRange(self, range, index, type, value):
        if not range.has_key(index): range[index] = {SimpleBlipParser.START: [],
                                                     SimpleBlipParser.END: []}
        range[index][type].append(value)
    
    def isIncluded(self, blip, hash_str):
        included = False
        from settings import SELF_DOMAIN
        from robotUtil import tag
        annotations = []
        for annotation in blip.annotations:
            if not re.match('link/', annotation.name): continue
            link = tag.getHtmlLink(annotation.value)
            query = link[link.find('?')+1:]
            params = dict((param.split('=')[0], urllib2.unquote(param.split('=')[1]) if len(param.split('=')) > 1 else '') for param in query.split('&'))
            if re.match(SELF_DOMAIN + 'static/include.html', link):
                if params.has_key('include') and params['include'] == '1':
                    included = True
            if params.has_key('gwextMark'):
                annotations.append(annotation)
#                    blip.range(annotation.start - shift, annotation.end - shift).delete()
#                    shift += annotation.end - annotation.start
#                    logging.info(shift)
        while annotations:
            annotation = annotations.pop()
            blip.range(annotation.start, annotation.end).delete()
        return included
    
    @staticmethod
    def getElementIndent(el):
        ''' getting element indent '''
        if type(el) == int: return el
        indent = el.get(LineProps.INDENT)
        if not indent:
            indent = 0
        else:
            indent = int(indent)
        el_type = el.get(LineProps.LINE_TYPE)
        if el_type and el_type == element.Line.TYPE_LI: return indent + 1
        return indent

    def switchToParentNode(self, current_node):
        child_node = current_node
        current_node = current_node.parentNode
        if not child_node.hasChildNodes():
            current_node.removeChild(child_node)
        return current_node

    def getHtmlLink(self, link):
        if re.match('waveid://', link): return '#'
        if re.match('http://www.google.com/url\?sa=D&q=', link):
            link = link.replace('http://www.google.com/url?sa=D&q=', '')
            link = urllib2.unquote(link)
        return link

    def generateCutterNode(self, document, current_node):
        '''
        Generate cut node for 'Read more...' feature (e.g. Blogspot <!-- more --> tag)
        @param current_node: currently processing node
        @return: new node
        '''
        new_node = document.createTextNode(self.cut_tag)
        current_node.appendChild(new_node);
        self.cut_tag = None
        return current_node

    def generateAttachmentNode(self, document, current_node, item):
        '''
        Convert attachment element into valid html node
        @param current_node: currently processing node
        @param item: currently processing element
        @return: new node
        '''
        mime_type = item.get(AttachmentProps.MIME_TYPE)
        if not mime_type:
            logging.info('skipped annotation with null mime type')
            return current_node
        if not re.match(AttachmentProps.IMAGE_TYPE, mime_type):
            logging.info('skipped non image mime type: %s' % mime_type)
            return current_node
        logging.info('processing attachment with mime type: %s' % mime_type)
        new_node = document.createElement('img')
        new_node.setAttribute('src', item.get(AttachmentProps.URL))
        new_node.setAttribute('alt', item.get(AttachmentProps.CAPTION))
        new_node.setAttribute('title', item.get(AttachmentProps.CAPTION))
        current_node.appendChild(new_node)
        return current_node

    def generateListIndentNode(self, document, current_node, item, current_indent):
        '''
        Process line indent
        @param current_node: currently processing node
        @param item: currently processing element
        @return: new node
        '''
        indent = SimpleBlipParser.getElementIndent(item)
        while current_indent < indent:
            new_node = document.createElement('ul')
            current_node.appendChild(new_node)
            current_node = new_node
            current_indent += 1
        while current_indent > indent:
            current_node = current_node.parentNode
            current_indent -= 1
        return current_node, current_indent

    def generateNonListIndentNode(self, document, current_node, item, indent):
        return self.generateListIndentNode(document, current_node, item, indent)

    def generateSimpleNode(self, document, current_node, item, indent):
        current_node, indent = self.generateNonListIndentNode(document, current_node, item, indent)
        new_node = document.createElement('div')
        current_node.appendChild(new_node)
        return new_node, indent

    def generateListNode(self, document, current_node, item, indent):
        current_node, indent = self.generateListIndentNode(document, current_node, item, indent)
        new_node = document.createElement('li')
        current_node.appendChild(new_node)
        return new_node, indent

    def generateHeadingNode(self, document, current_node, item, indent):
        current_node, indent = self.generateNonListIndentNode(document, current_node, item, indent)
        line_type = item.get(LineProps.LINE_TYPE)
        ''' processing H# type '''
        if not line_type or line_type[0] != u'h':
            logging.critical('Found unexpected Line Type: %s' % line_type)
        new_node = document.createElement(line_type)
        current_node.appendChild(new_node)
        return new_node, indent

    def generateAlignment(self, current_node, item):
        alignment = item.get(LineProps.ALIGNMENT)
        if alignment:
            current_node.setAttribute('style', 'text-align: %s;' % LINE_ALIGNMENT_MAP[alignment])
        return current_node

    def generateLineNode(self, document, current_node, item, indent):
        ''' processing line element '''
        line_type = item.get(LineProps.LINE_TYPE)
        if not line_type:
            current_node, indent = self.generateSimpleNode(document, current_node, item, indent)
        elif line_type == element.Line.TYPE_LI:
            current_node, indent = self.generateListNode(document, current_node, item, indent)
        else:
            current_node, indent = self.generateHeadingNode(document, current_node, item, indent)
        return self.generateAlignment(current_node, item), indent

    def generateInlineBlip(self, document, current_node, item, current_indent, blip):
        blip_id = item.get('id')
        block_tag = None
        block_style = ''
        style = None
        while not block_tag:
            if current_node.tagName == 'span':
                style = current_node.getAttribute('style')
                current_node = self.switchToParentNode(current_node)
                continue
            if current_node.tagName == 'a':
                current_node = self.switchToParentNode(current_node)
                continue
            block_style = current_node.getAttribute('style')
            block_tag = current_node.tagName
            if current_node.tagName == 'li':
                current_node = self.generateListIndentNode(document, current_node, 0, current_indent)[0]
            current_node = self.switchToParentNode(current_node)
            break
        for thread in blip.inline_reply_threads:
            if thread.id != blip_id: continue
            for child in thread.blips:
                new_node = self.parseBlip(document, child)
                if not new_node: continue
                current_node.appendChild(new_node)
                self.switchToParentNode(new_node)
            break
        if(block_tag == 'li'):
            logging.info('current tag is li')
            current_node = self.generateListIndentNode(document, current_node, current_indent, 0)[0]
        new_node = document.createElement(block_tag)
        if block_style: new_node.setAttribute('style', block_style)
        current_node.appendChild(new_node)
        current_node = new_node
        if style:
            new_node = document.createElement('span')
            new_node.setAttribute('style', style)
            current_node.appendChild(new_node)
            current_node = new_node
        return current_node

    def generateElementNode(self, document, current_node, item, current_indent, blip):
        '''
        Generate element node
        @param document: document instance 
        @param current_node: currently processing node
        @param item: currently processing element
        @return: new node
        '''
        item_type = type(item)
        if item_type == element.Line:
            return self.generateLineNode(document, current_node, item, current_indent)
        elif item_type == element.Attachment:
            return self.generateAttachmentNode(document, current_node, item), current_indent
        elif item_type == element.Element and item.type == element.Element.INLINE_BLIP_TYPE:
            try:
                return self.generateInlineBlip(document, current_node, item, current_indent, blip), current_indent
            except:
                logging.warn(traceback.format_exc())
            return current_node, current_indent
        elif item_type == Cutter:
            return self.generateCutterNode(document, current_node), current_indent
        elif item_type == element.Image:
            # processing image element
            logging.critical('found image')
        return current_node, current_indent

    def generateLinkNode(self, document, current_node, current_link):
        new_node = document.createElement('a')
        new_node.setAttribute('href', current_link)
        current_node.appendChild(new_node)
        return new_node

    def generateStyleNode(self, document, current_node, current_style):
        new_node = document.createElement('span')
        new_node.setAttribute('style', ''.join(current_style.values()))
        current_node.appendChild(new_node)
        return new_node

    def generateRootNode(self, document):
        return document.createElement('span')

    def parseBlip(self, document, blip, cut_tag=None, force=False, current_indent=0):
        root_node = self.generateRootNode(document)
        current_node = root_node
        if blip.blip_id in self.processed_blips: return root_node
        self.processed_blips.append(blip.blip_id)
        if cut_tag: self.cut_tag = cut_tag
        current_style = {}
        current_link = ''
        pos_start = 0
        last_block_element = 0
#        index = blip.text.find(MARK)
#        if force or (index > 0 and not blip.text[:index].strip()):
        if self.isIncluded(blip, '') or force:
#            if index > 0:
#                for i in range(index + len(MARK) - 1, 0, -1):
#                    blipRefs = blip[i].value()
#                    if(type(blipRefs) == element.Line): continue
#                    blip.range(i, i + 1).delete()
            element_range = self.__initRange(blip, cut_tag)
            for i in range(len(blip) + 1):
                if not element_range.has_key(i): continue
                # placing text into the current node
                if i - pos_start > 0:
                    text_node = document.createTextNode(html_escape(blip.text[pos_start:i]))
                    current_node.appendChild(text_node)
                    pos_start = i
                pos_items = element_range[i]

                ''' processing endings '''
                first_run = True
                for item in pos_items[SimpleBlipParser.END]: #range(::-1)
                    if first_run:
                        first_run = False
                        if current_style:
                            ''' process styles '''
                            if current_node.parentNode:
                                current_node = self.switchToParentNode(current_node)
                        if current_link:
                            ''' process links '''
                            if current_node.parentNode:
                                current_node = self.switchToParentNode(current_node)
                    ''' returning from element's stack '''
                    if type(item) == element.Line:
                        if not blip.text[last_block_element:i].strip():
                            current_node.appendChild(document.createElement('br'))
                        current_node = current_node.parentNode
                    if type(item) == Annotation:
                        annotation_type = item.name
                        if annotation_type in [Annotation.LINK, 'link/auto']:
                            ''' process closing link '''
                            current_link = ''
                        else:
                            ''' processing closing style '''
                            del current_style[item.name]
                # processing startings
                for item in pos_items[SimpleBlipParser.START]:
                    if first_run:
                        if current_style:
                            current_node = self.switchToParentNode(current_node)
                        first_run = False

                    if isinstance(item, element.Element):
                        last_block_element = i
                        pos_start += 1
                        current_node, current_indent = self.generateElementNode(document, current_node, item, current_indent, blip)
                    else: # processing annotations
                        annotation_type = item.name
                        if annotation_type in [Annotation.LINK, 'link/auto']:
                            current_link = self.getHtmlLink(item.value)
                        else:
                            current_style[item.name] = ANNOTATION_MAP[item.name] % item.value
                if current_link:
                    current_node = self.generateLinkNode(document, current_node, current_link)
                if current_style:
                    current_node = self.generateStyleNode(document, current_node, current_style)
        for child in blip.child_blips:
            new_node = self.parseBlip(document, child)
            if not new_node: continue
            current_node.appendChild(new_node)
            self.switchToParentNode(new_node)
        return root_node
#        title = self.parseTitle(root_node)
#        return root_node.toxml(encoding='UTF-8') + '<div style="margin-top: 15px; margin-bottom: 15px; line-height: 15px; text-align: center">Сделано с помощью  BloggerBot. &copy; <a href="http://www.projectvolna.com">www.ProjectVolna.com</a></div>', title

class GDocsBlipParser(SimpleBlipParser):
    def __init__(self):
        super(GDocsBlipParser, self).__init__()

    def exitListIndentNode(self, document, current_node, item, current_indent):
        while current_indent > 0:
            current_node = current_node.parentNode
            logging.info(current_node.tagName)
            current_indent -= 1
        return current_node, 0
    
    def generateInlineBlip(self, document, current_node, item, current_indent, blip):
        logging.info('generateInlineBlip')
        blip_id = item.get('id')
        block_tag = None
        block_style = ''
        style = None
        logging.info(current_indent)
        while not block_tag:
            logging.info(current_node.tagName)
            if current_node.tagName == 'span':
                style = current_node.getAttribute('style')
                current_node = self.switchToParentNode(current_node)
                continue
            if current_node.tagName == 'a':
                current_node = self.switchToParentNode(current_node)
                continue
            block_style = current_node.getAttribute('style')
            block_tag = current_node.tagName
            if current_node.tagName == 'li':
                current_node = self.exitListIndentNode(document, current_node, item, current_indent)[0]
            current_node = self.switchToParentNode(current_node)
            break
        logging.info('finished exiting')
        logging.info(current_node.tagName)
        for thread in blip.inline_reply_threads:
            if thread.id != blip_id: continue
            for child in thread.blips:
                new_node = self.parseBlip(document, child)
                if not new_node: continue
                current_node.appendChild(new_node)
                self.switchToParentNode(new_node)
            break
        logging.info(block_tag)
        if(block_tag == 'li'):
            logging.info('current tag is li')
            current_node = self.generateListIndentNode(document, current_node, current_indent, 0)[0]
        new_node = document.createElement(block_tag)
        if block_style: new_node.setAttribute('style', block_style)
        current_node.appendChild(new_node)
        current_node = new_node
        if style:
            new_node = document.createElement('span')
            new_node.setAttribute('style', style)
            current_node.appendChild(new_node)
            current_node = new_node
        return current_node

    def generateNonListIndentNode(self, document, current_node, item, indent):
        indent = SimpleBlipParser.getElementIndent(item)
        if indent:
            current_node.setAttribute('style', 'text-indent: %dpx; margin-left: %dpx;' % (indent * 48, indent * 48))
        return current_node

    def generateSimpleNode(self, document, current_node, item, indent):
        current_node, indent = self.exitListIndentNode(document, current_node, item, indent)
        new_node = document.createElement('div')
        current_node.appendChild(self.generateNonListIndentNode(document, new_node, item, indent))
        return new_node, 0

    def generateHeadingNode(self, document, current_node, item, indent):
        current_node, indent = self.exitListIndentNode(document, current_node, item, indent)
        line_type = item.get(LineProps.LINE_TYPE)
        # processing H# type
        if not line_type or line_type[0] != u'h':
            logging.critical('Found unexpected Line Type: %s' % line_type)
        new_node = document.createElement(line_type)
        current_node.appendChild(self.generateNonListIndentNode(document, new_node, item, indent))
        return new_node, 0

class WaveletParser(object):
    def __init__(self, parser=SimpleBlipParser):
        self.parser = parser()

    def collectText(self, root_node, title):
        for node in root_node.childNodes:
            if node.nodeType == Node.TEXT_NODE:
                title += node.data
            else:
                title = self.collectText(node, title)
        return title

    def parseTitle(self, root_node):
        BLOCK_ELEMENTS = ['div', 'p', 'li', 'h1', 'h2', 'h3', 'h4', 'h5']
        skip = 0
        title = ''
        while skip >= 0 and not title and root_node.hasChildNodes():
            # find block elements
            if not root_node.hasChildNodes(): break
            current_node = root_node.childNodes[0]
            while current_node.hasChildNodes() and current_node.tagName not in BLOCK_ELEMENTS:
                current_node = current_node.childNodes[0]
            if current_node.tagName in BLOCK_ELEMENTS:
                title = self.collectText(current_node, title)
            title = title.strip()
            current_node.parentNode.removeChild(current_node)
            if not title: continue
            logging.info('Title: ' + title)
            if skip: title = ''
            skip -= 1
        return title

    def parseWavelet(self, wavelet, cut_tag=None):
        document = minidom.Document()
        root_node = document.createElement('span')
        for blip in wavelet.root_thread.blips:
            parsed_blip = self.parser.parseBlip(document, blip, cut_tag=cut_tag, force=(wavelet.root_blip.blip_id == blip.blip_id))
            root_node.appendChild(parsed_blip)
        title = self.parseTitle(root_node)
        return root_node.toxml(encoding='UTF-8') + '<div style="line-height: 15px; text-align: center"><br />Сделано с помощью  BloggerBot. &copy; <a href="http://www.projectvolna.com">www.ProjectVolna.com</a><br /><br /></div>', title
    
    def parseBlip(self, blip, cut_tag=None):
        document = minidom.Document()
        root_node = document.createElement('span')
#        for blip in wavelet.root_thread.blips:
        logging.info(blip.blip_id)
        parsed_blip = self.parser.parseBlip(document, blip, cut_tag=cut_tag, force=True)
        root_node.appendChild(parsed_blip)
        logging.info(root_node.toxml())
        title = self.parseTitle(root_node)
        return root_node.toxml(encoding='UTF-8') + '<div style="line-height: 15px; text-align: center"><br />Сделано с помощью  BloggerBot. &copy; <a href="http://www.projectvolna.com">www.ProjectVolna.com</a><br /><br /></div>', title
    
